Skip to main content
Version: Next

Compose Screen - View State representation

A Screen is observing a single View State provided by the View Model. The View State is typically modelled as a combination of Kotlin sealed classes and data classes. Please check the example below:

data class ExampleUiState(
val isLoading: Boolean,
val headlineUiState: HeadlineUiState,
val bodyUiState: BodyUiState,
) {
data class HeadlineUiState(
val title: String,
val subtitle: String,
val iconOne: Drawable,
val iconTwo: Drawable,
)

data class BodyUiState(
val items: List<BodyItem>,
) {
data class BodyItem(
val id: String,
val text: String,
)
}
}

Compose Screen - View Event representation

With ever-growing number of possible User interactions with the Screen, modeling of those events becomes a problem. It's especially visible in the number of lambda-callbacks that are passed down the Composable hierarchy. To tackle this problem, it's recommended to introduce a hierarchy of View Events, as demonstrated with an example below:

sealed class ExampleUiEvent {
sealed class HeadlineUiEvent {
data object HeadlineClicked : HeadlineUiEvent()
data object IconOneClicked : HeadlineUiEvent()
data object IconTwoClicked : HeadlineUiEvent()
}

sealed class BodyUiEvent {
data class ItemClicked(
val itemId: String,
val clickType: ClickType,
) : BodyUiEvent() {
enum class ClickType {
Like,
Unlike,
SaveForLater,
UnsaveForLater,
Shared
}
}

data object OnLoadMoreItems : BodyUiEvent()
}
}

Compose Screen - use of View State and Events

Thanks to the declarations provided above, it's possible to implement the Screen functionality in an elegant way:

@Composable
fun ExampleScreen2() {
val viewModel: ExampleViewModel = hiltViewModel()
val exampleUiState = viewModel.exampleUiState.collectAsState()

ExampleScreenHeadline2(
headlineUiState = exampleUiState.value.headlineUiState,
// viewModel exposes API that directly consumes HeadlineUiEvent
onHeadlineUiEvent = viewModel::onHeadlineUiEvent
)

ExampleScreenBody2(
bodyUiState = exampleUiState.value.bodyUiState,
// viewModel exposes API that needs some mapping
onBodyUiEvent = {
when (it) {
is ExampleUiEvent.BodyUiEvent.ItemClicked -> viewModel.onItemClicked(it.itemId, it.clickType)
ExampleUiEvent.BodyUiEvent.OnLoadMoreItems -> viewModel.onLoadMoreItems()
}
},
)
}

@Composable
fun ExampleScreenHeadline2(
headlineUiState: ExampleUiState.HeadlineUiState,
onHeadlineUiEvent: (ExampleUiEvent.HeadlineUiEvent) -> Unit,
) {
// ...
}

@Composable
fun ExampleScreenBody2(
bodyUiState: ExampleUiState.BodyUiState,
onBodyUiEvent: (ExampleUiEvent.BodyUiEvent) -> Unit,
) {
// ...
}